@@ -3,6 +3,7 @@ |
||
3 | 3 |
from django.conf.urls import url |
4 | 4 |
|
5 | 5 |
from api import mini_views, oauth_views, pack_views |
6 |
+from pay import views as pay_views |
|
6 | 7 |
|
7 | 8 |
|
8 | 9 |
urlpatterns = [ |
@@ -15,6 +16,10 @@ urlpatterns += [ |
||
15 | 16 |
] |
16 | 17 |
|
17 | 18 |
urlpatterns += [ |
19 |
+ url(r'^pay/wx/order_create$', pay_views.wx_order_create_api, name='wx_order_create_api'), # 订单创建 |
|
20 |
+] |
|
21 |
+ |
|
22 |
+urlpatterns += [ |
|
18 | 23 |
url(r'^3rd/or$', oauth_views.oauth_redirect, name='3rd_or'), |
19 | 24 |
url(r'^3rd/oauth_redirect$', oauth_views.oauth_redirect, name='3rd_oauth_redirect'), |
20 | 25 |
] |
@@ -58,6 +58,7 @@ INSTALLED_APPS = [ |
||
58 | 58 |
'account', |
59 | 59 |
'goods', |
60 | 60 |
'kol', |
61 |
+ 'pay', |
|
61 | 62 |
] |
62 | 63 |
|
63 | 64 |
MIDDLEWARE = [ |
@@ -0,0 +1,10 @@ |
||
1 |
+from django.contrib import admin |
|
2 |
+ |
|
3 |
+ |
|
4 |
+# Register your models here. |
|
5 |
+from pay.models import OrderInfo |
|
6 |
+ |
|
7 |
+class OrderInfoAdmin(admin.ModelAdmin): |
|
8 |
+ list_display = ('kol_id', 'pack_id', 'goods_info', 'user_id', 'total_fee', 'name', 'phone', 'address', 'tracking_number', 'pay_status') |
|
9 |
+ |
|
10 |
+admin.site.register(OrderInfo, OrderInfoAdmin) |
@@ -0,0 +1,50 @@ |
||
1 |
+# Generated by Django 2.2.12 on 2020-04-21 15:47 |
|
2 |
+ |
|
3 |
+from django.db import migrations, models |
|
4 |
+import jsonfield.fields |
|
5 |
+import shortuuidfield.fields |
|
6 |
+ |
|
7 |
+ |
|
8 |
+class Migration(migrations.Migration): |
|
9 |
+ |
|
10 |
+ initial = True |
|
11 |
+ |
|
12 |
+ dependencies = [ |
|
13 |
+ ] |
|
14 |
+ |
|
15 |
+ operations = [ |
|
16 |
+ migrations.CreateModel( |
|
17 |
+ name='OrderInfo', |
|
18 |
+ fields=[ |
|
19 |
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|
20 |
+ ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')), |
|
21 |
+ ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')), |
|
22 |
+ ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')), |
|
23 |
+ ('order_id', shortuuidfield.fields.ShortUUIDField(blank=True, db_index=True, editable=False, help_text='订单唯一标识', max_length=22)), |
|
24 |
+ ('prepay_id', models.CharField(blank=True, help_text='预支付交易会话标识', max_length=64, null=True, verbose_name='prepay_id')), |
|
25 |
+ ('transaction_id', models.CharField(blank=True, help_text='交易单号', max_length=32, null=True, verbose_name='transaction_id')), |
|
26 |
+ ('pack_id', models.CharField(blank=True, db_index=True, help_text='包唯一标识', max_length=32, null=True, verbose_name='pack_id')), |
|
27 |
+ ('goods_info', jsonfield.fields.JSONField(blank=True, default=[], help_text='商品信息', null=True, verbose_name='goods_info')), |
|
28 |
+ ('user_id', models.CharField(blank=True, db_index=True, help_text='用户唯一标识', max_length=32, null=True, verbose_name='kol_id')), |
|
29 |
+ ('kol_id', models.CharField(blank=True, db_index=True, help_text='kol_id唯一标识', max_length=32, null=True, verbose_name='kol_id')), |
|
30 |
+ ('body', models.CharField(blank=True, help_text='商品描述', max_length=255, null=True, verbose_name='body')), |
|
31 |
+ ('total_fee', models.IntegerField(default=0, help_text='总金额', verbose_name='total_fee')), |
|
32 |
+ ('name', models.CharField(blank=True, help_text='姓名', max_length=255, null=True, verbose_name='name')), |
|
33 |
+ ('phone', models.CharField(blank=True, help_text='电话', max_length=255, null=True, verbose_name='phone')), |
|
34 |
+ ('address', models.CharField(blank=True, help_text='地址', max_length=255, null=True, verbose_name='address')), |
|
35 |
+ ('tracking_number', models.CharField(blank=True, help_text='快递单号', max_length=255, null=True, verbose_name='tracking_number')), |
|
36 |
+ ('has_send_template_message', models.BooleanField(db_index=True, default=True, help_text='是否已发送模版消息', verbose_name='has_send_template_message')), |
|
37 |
+ ('trade_type', models.CharField(blank=True, help_text='支付方式', max_length=255, null=True, verbose_name='trade_type')), |
|
38 |
+ ('pay_status', models.IntegerField(choices=[(0, '待支付'), (1, '已支付'), (2, '已失败')], db_index=True, default=0, help_text='支付状态', verbose_name='pay_status')), |
|
39 |
+ ('paid_at', models.DateTimeField(blank=True, help_text='支付时间', null=True, verbose_name='paid_at')), |
|
40 |
+ ('reback_status', models.BooleanField(db_index=True, default=False, help_text='退款状态', verbose_name='reback_status')), |
|
41 |
+ ('reback_at', models.DateTimeField(blank=True, help_text='退款时间', null=True, verbose_name='reback_at')), |
|
42 |
+ ('unifiedorder_result', models.TextField(blank=True, help_text='统一下单结果', null=True, verbose_name='unifiedorder_result')), |
|
43 |
+ ('notify_msg', models.TextField(blank=True, help_text='回调信息', null=True, verbose_name='notify_msg')), |
|
44 |
+ ], |
|
45 |
+ options={ |
|
46 |
+ 'verbose_name': '订单信息', |
|
47 |
+ 'verbose_name_plural': '订单信息', |
|
48 |
+ }, |
|
49 |
+ ), |
|
50 |
+ ] |
@@ -0,0 +1,74 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+ |
|
3 |
+from django.db import models |
|
4 |
+from django.utils.translation import ugettext_lazy as _ |
|
5 |
+from django_models_ext import BaseModelMixin |
|
6 |
+from shortuuidfield import ShortUUIDField |
|
7 |
+from jsonfield import JSONField |
|
8 |
+from TimeConvert import TimeConvert as tc |
|
9 |
+ |
|
10 |
+class OrderInfo(BaseModelMixin): |
|
11 |
+ |
|
12 |
+ """ |
|
13 |
+ # Trade State of Wechat Query |
|
14 |
+ SUCCESS ——— 支付成功 |
|
15 |
+ REFUND ——— 转入退款 |
|
16 |
+ NOTPAY ——— 未支付 |
|
17 |
+ CLOSED ——— 已关闭 |
|
18 |
+ REVOKED ——— 已撤销(刷卡支付) |
|
19 |
+ USERPAYING ——— 用户支付中 |
|
20 |
+ PAYERROR ——— 支付失败(其他原因,如银行返回失败) |
|
21 |
+ """ |
|
22 |
+ |
|
23 |
+ WAITING_PAY = 0 |
|
24 |
+ PAID = 1 |
|
25 |
+ FAIL = 2 |
|
26 |
+ # DELETED = 9 |
|
27 |
+ |
|
28 |
+ PAY_STATUS = ( |
|
29 |
+ (WAITING_PAY, u'待支付'), |
|
30 |
+ (PAID, u'已支付'), |
|
31 |
+ (FAIL, u'已失败'), |
|
32 |
+ # (DELETED, u'已删除'), |
|
33 |
+ ) |
|
34 |
+ |
|
35 |
+ order_id = ShortUUIDField(_(u'order_id'), max_length=32, help_text=u'订单唯一标识', db_index=True) |
|
36 |
+ |
|
37 |
+ prepay_id = models.CharField(_(u'prepay_id'), max_length=64, blank=True, null=True, help_text=u'预支付交易会话标识') |
|
38 |
+ transaction_id = models.CharField(_(u'transaction_id'), max_length=32, blank=True, null=True, help_text=u'交易单号') |
|
39 |
+ |
|
40 |
+ pack_id = models.CharField(_(u'pack_id'), max_length=32, blank=True, null=True, help_text=u'包唯一标识', db_index=True) |
|
41 |
+ goods_info = JSONField(_('goods_info'), default=[], blank=True, null=True, help_text='商品信息') |
|
42 |
+ |
|
43 |
+ user_id = models.CharField(_(u'kol_id'), max_length=32, blank=True, null=True, help_text=u'用户唯一标识', db_index=True) |
|
44 |
+ kol_id = models.CharField(_(u'kol_id'), max_length=32, blank=True, null=True, help_text=u'kol_id唯一标识', db_index=True) |
|
45 |
+ |
|
46 |
+ body = models.CharField(_(u'body'), max_length=255, blank=True, null=True, help_text=u'商品描述') |
|
47 |
+ total_fee = models.IntegerField(_(u'total_fee'), default=0, help_text=u'总金额') |
|
48 |
+ |
|
49 |
+ name = models.CharField(_(u'name'), max_length=255, blank=True, null=True, help_text=u'姓名') |
|
50 |
+ phone = models.CharField(_(u'phone'), max_length=255, blank=True, null=True, help_text=u'电话') |
|
51 |
+ address = models.CharField(_(u'address'), max_length=255, blank=True, null=True, help_text=u'地址') |
|
52 |
+ |
|
53 |
+ tracking_number = models.CharField(_(u'tracking_number'), max_length=255, blank=True, null=True, help_text=u'快递单号') |
|
54 |
+ has_send_template_message = models.BooleanField(_(u'has_send_template_message'), default=True, help_text=u'是否已发送模版消息', db_index=True) |
|
55 |
+ |
|
56 |
+ trade_type = models.CharField(_(u'trade_type'), max_length=255, blank=True, null=True, help_text=u'支付方式') |
|
57 |
+ |
|
58 |
+ pay_status = models.IntegerField(_(u'pay_status'), choices=PAY_STATUS, default=WAITING_PAY, help_text=u'支付状态', db_index=True) |
|
59 |
+ paid_at = models.DateTimeField(_(u'paid_at'), blank=True, null=True, help_text=_(u'支付时间')) |
|
60 |
+ |
|
61 |
+ reback_status = models.BooleanField(_(u'reback_status'), default=False, help_text=u'退款状态', db_index=True) |
|
62 |
+ reback_at = models.DateTimeField(_(u'reback_at'), blank=True, null=True, help_text=_(u'退款时间')) |
|
63 |
+ |
|
64 |
+ # 微信统一下单 |
|
65 |
+ unifiedorder_result = models.TextField(_(u'unifiedorder_result'), blank=True, null=True, help_text=_(u'统一下单结果')) |
|
66 |
+ # 微信支付回调 |
|
67 |
+ notify_msg = models.TextField(_(u'notify_msg'), blank=True, null=True, help_text=u'回调信息') |
|
68 |
+ |
|
69 |
+ class Meta: |
|
70 |
+ verbose_name = _(u'订单信息') |
|
71 |
+ verbose_name_plural = _(u'订单信息') |
|
72 |
+ |
|
73 |
+ def __unicode__(self): |
|
74 |
+ return u'{0.pk}'.format(self) |
@@ -0,0 +1,4 @@ |
||
1 |
+from django.test import TestCase |
|
2 |
+ |
|
3 |
+ |
|
4 |
+# Create your tests here. |
@@ -0,0 +1,138 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+ |
|
3 |
+from django.conf import settings |
|
4 |
+from django.db import transaction |
|
5 |
+from django.shortcuts import HttpResponse |
|
6 |
+from django_logit import logit |
|
7 |
+from django_response import response |
|
8 |
+from pywe_exception import WeChatPayException |
|
9 |
+from pywe_pay import WeChatPay |
|
10 |
+from pywe_pay_notify import check_pay_notify |
|
11 |
+from pywe_response import WXPAY_NOTIFY_FAIL, WXPAY_NOTIFY_SUCCESS |
|
12 |
+from pywe_sign import check_signature |
|
13 |
+from TimeConvert import TimeConvert as tc |
|
14 |
+import json |
|
15 |
+from functools import reduce |
|
16 |
+ |
|
17 |
+from account.models import UserInfo |
|
18 |
+from goods.models import PackInfo, PackGoodsInfo, GoodsInfo |
|
19 |
+from kol.models import KOLInfo |
|
20 |
+from pay.models import OrderInfo |
|
21 |
+from utils.error.errno_utils import (PackStatusCode, KOLStatusCode, PackGoodsStatusCode, OrderStatusCode, UserStatusCode, |
|
22 |
+ WithdrawStatusCode) |
|
23 |
+ |
|
24 |
+ |
|
25 |
+WECHAT = settings.WECHAT |
|
26 |
+ |
|
27 |
+ |
|
28 |
+@logit |
|
29 |
+@transaction.atomic |
|
30 |
+def wx_order_create_api(request): |
|
31 |
+ """ 订单创建 """ |
|
32 |
+ kol_id = request.POST.get('kol_id', '') |
|
33 |
+ user_id = request.POST.get('user_id', '') |
|
34 |
+ pack_id = request.POST.get('pack_id', '') |
|
35 |
+ goods_info = json.loads(request.POST.get('goods_info', '[]')) |
|
36 |
+ |
|
37 |
+ name = request.POST.get('name', '') |
|
38 |
+ phone = request.POST.get('phone', '') |
|
39 |
+ address = request.POST.get('address', '') |
|
40 |
+ |
|
41 |
+ # 用户校验 |
|
42 |
+ try: |
|
43 |
+ user = UserInfo.objects.get(user_id=user_id, status=True) |
|
44 |
+ except UserInfo.DoesNotExist: |
|
45 |
+ return response(UserStatusCode.USER_NOT_FOUND) |
|
46 |
+ |
|
47 |
+ # kol校验 |
|
48 |
+ try: |
|
49 |
+ kol = KOLInfo.objects.get(kol_id=kol_id, status=True) |
|
50 |
+ except KOLInfo.DoesNotExist: |
|
51 |
+ return response(KOLStatusCode.KOL_NOT_FOUND) |
|
52 |
+ |
|
53 |
+ # 包校验 |
|
54 |
+ try: |
|
55 |
+ pack = PackInfo.objects.get(kol_id=kol_id, pack_id=pack_id, status=True) |
|
56 |
+ except PackInfo.DoesNotExist: |
|
57 |
+ return response(PackStatusCode.PACK_NOT_FOUND) |
|
58 |
+ |
|
59 |
+ body = request.POST.get('body', '') # 商品描述 |
|
60 |
+ total_fee = int(request.POST.get('total_fee', 0)) # 总金额,单位分 |
|
61 |
+ |
|
62 |
+ #包-商品校验 |
|
63 |
+ for g in goods_info: |
|
64 |
+ try: |
|
65 |
+ PackGoodsInfo.objects.get(pack_id=pack_id, good_id=g.get('good_id', '')) |
|
66 |
+ except PackGoodsInfo.DoesNotExist: |
|
67 |
+ return response(PackGoodsStatusCode.PACK_GOODS_NOT_FOUND) |
|
68 |
+ |
|
69 |
+ # 金额校验 |
|
70 |
+ if reduce(lambda g1, g2: g1.price * g1.num + g2.price * g2.num, goods_info) != total_fee: |
|
71 |
+ return response(OrderStatusCode.FEE_CHECK_FAIL) |
|
72 |
+ |
|
73 |
+ # JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付,统一下单接口trade_type的传参可参考这里 |
|
74 |
+ trade_type = 'JSAPI' |
|
75 |
+ |
|
76 |
+ # 根据 trade_type 获取 wechat 配置 |
|
77 |
+ wxcfg = WECHAT.get(trade_type, {}) |
|
78 |
+ # WeChatPay 初始化 |
|
79 |
+ wxpay = WeChatPay(wxcfg.get('appID'), wxcfg.get('apiKey'), wxcfg.get('mchID')) |
|
80 |
+ |
|
81 |
+ # 生成订单 |
|
82 |
+ order = OrderInfo.objects.create( |
|
83 |
+ user_id=user_id, |
|
84 |
+ pack_id=pack_id, |
|
85 |
+ kol_id=kol_id, |
|
86 |
+ goods_info=goods_info, |
|
87 |
+ total_fee=total_fee, |
|
88 |
+ trade_type=trade_type, |
|
89 |
+ name=name, |
|
90 |
+ phone=phone, |
|
91 |
+ address=address, |
|
92 |
+ ) |
|
93 |
+ |
|
94 |
+ try: |
|
95 |
+ prepay_data = wxpay.order.create( |
|
96 |
+ body=body, |
|
97 |
+ notify_url=settings.API_DOMAIN + '/wx/notify_url', |
|
98 |
+ out_trade_no=order.order_id, |
|
99 |
+ total_fee=total_fee, |
|
100 |
+ trade_type=trade_type, |
|
101 |
+ openid=user.openid, # 可选,用户在商户appid下的唯一标识。trade_type=JSAPI,此参数必传 |
|
102 |
+ ) |
|
103 |
+ except WeChatPayException as e: |
|
104 |
+ order.unifiedorder_result = e.message |
|
105 |
+ order.save() |
|
106 |
+ return response(OrderStatusCode.WX_UNIFIED_ORDER_FAIL) |
|
107 |
+ |
|
108 |
+ prepay_id = prepay_data.get('prepay_id', '') |
|
109 |
+ order.prepay_id = prepay_id |
|
110 |
+ order.save() |
|
111 |
+ |
|
112 |
+ if trade_type == 'JSAPI' or trade_type == 'MINIAPP': |
|
113 |
+ wxpay_params = wxpay.jsapi.get_jsapi_params(prepay_id) |
|
114 |
+ elif trade_type == 'APP': |
|
115 |
+ wxpay_params = wxpay.order.get_appapi_params(prepay_id) |
|
116 |
+ |
|
117 |
+ return response(200, 'Order Create Success', u'订单创建成功', { |
|
118 |
+ 'order_id': order.order_id, |
|
119 |
+ 'prepay_id': prepay_id, |
|
120 |
+ 'wxpay_params': wxpay_params, |
|
121 |
+ }) |
|
122 |
+ |
|
123 |
+ |
|
124 |
+def order_paid_success(order): |
|
125 |
+ if order.pay_status == OrderInfo.PAID: |
|
126 |
+ return |
|
127 |
+ |
|
128 |
+ order.pay_status = OrderInfo.PAID |
|
129 |
+ order.paid_at = tc.utc_datetime() |
|
130 |
+ order.save() |
|
131 |
+ |
|
132 |
+ |
|
133 |
+def order_paid_fail(order): |
|
134 |
+ if order.pay_status == OrderInfo.FAIL: |
|
135 |
+ return |
|
136 |
+ |
|
137 |
+ order.pay_status = OrderInfo.FAIL |
|
138 |
+ order.save() |
@@ -1,3 +1,5 @@ |
||
1 | 1 |
pywe_miniapp==1.1.5 |
2 | 2 |
pywe-oauth==1.1.1 |
3 | 3 |
pywe-pay==1.0.13 |
4 |
+pywe-pay-notify==1.0.4 |
|
5 |
+pywe-response==1.0.1 |
@@ -2,6 +2,17 @@ |
||
2 | 2 |
|
3 | 3 |
from StatusCode import BaseStatusCode, StatusCodeField |
4 | 4 |
|
5 |
+class UserStatusCode(BaseStatusCode): |
|
6 |
+ USER_NOT_FOUND = StatusCodeField(400001, 'User Not Found', description=u'用户不存在') |
|
7 |
+ |
|
8 |
+class KOLStatusCode(BaseStatusCode): |
|
9 |
+ KOL_NOT_FOUND = StatusCodeField(400001, 'KOL Not Found', description=u'KOL不存在') |
|
10 |
+ |
|
11 |
+class PackStatusCode(BaseStatusCode): |
|
12 |
+ PACK_NOT_FOUND = StatusCodeField(100001, 'Pack Not Found', description=u'包不存在') |
|
13 |
+ |
|
14 |
+class PackGoodsStatusCode(BaseStatusCode): |
|
15 |
+ PACK_GOODS_NOT_FOUND = StatusCodeField(100001, 'Pack Goods Not Found', description=u'包商品不存在') |
|
5 | 16 |
|
6 | 17 |
class ParamStatusCode(BaseStatusCode): |
7 | 18 |
""" 4000xx 参数相关错误码 """ |